/**
 * @file app\tasks\mobus\_multicast.c
 *
 * Copyright (C) 2023
 *
 * _multicast.c is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * @author HinsShum hinsshum@qq.com
 *
 * @encoding utf-8
 */
#include "relay.h"
#include "storage.h"

/*---------- includes ----------*/
/*---------- macro ----------*/
/*---------- type define ----------*/
/*---------- variable prototype ----------*/
static struct storage_mobus_groups_info* _groups = NULL;

/*---------- function prototype ----------*/
static void __multicast_cfg_add(mobus_device_t self, uint8_t* param,
    uint8_t length);
static void __multicast_cfg_del(mobus_device_t self, uint8_t* param,
    uint8_t length);
static void __multicast_cfg_read_nums(mobus_device_t self, uint8_t* param,
    uint8_t length);

/*---------- variable ----------*/
static struct protocol_callback _multicast_cfg_cbs[] = {
    {MOBUS_GROUP_CONFIG_CMD_ADD, __multicast_cfg_add},
    {MOBUS_GROUP_CONFIG_CMD_MODIFY, __multicast_cfg_add},
    {MOBUS_GROUP_CONFIG_CMD_DEL, __multicast_cfg_del},
    {MOBUS_GROUP_CONFIG_CMD_READ_NUMS, __multicast_cfg_read_nums},
    {MOBUS_GROUP_CONFIG_CMD_READ_CONTENT, NULL},
};

/*---------- function ----------*/
static inline bool __new_group_by_uuid(mobus_device_t self, uint16_t uuid,
    uint8_t bits) {
    bool err = false;
    uint8_t new_pos = 0xFF, exist_pos = 0xFF;
    uint8_t i = 0;
    if (uuid == 0)
        return err;
    for (i = 0; i < CONFIG_STORAGE_MOBUS_GROUP_NUMS; i++) {
        if (_groups->groups[i].uid == 0xFFFF) {
            if (new_pos == 0xFF) {
                new_pos = i;
            }
        }
        else if (_groups->groups[i].uid == uuid) {
            if (_groups->groups[i].bits != bits) {
                new_pos = i;
                break;
            }
            else {
                exist_pos = i;
                err = true;
                break;
            }
        }
    }
    if (new_pos != 0xFF && exist_pos == 0xFF) {
      /* new group */
        _groups->groups[new_pos].uid = uuid;
        _groups->groups[new_pos].bits = bits;
        storage_set(STORAGE_AREA_MOBUS_GROUPS, _groups,
            sizeof(struct storage_mobus_groups_info));
        err = true;
    }

    return err;
}

static inline bool __del_group_by_uuid(mobus_device_t self, uint16_t uuid) {
    bool err = false;
    for (uint16_t i = 0; i < CONFIG_STORAGE_MOBUS_GROUP_NUMS; i++) {
        if (_groups->groups[i].uid == uuid) {
            _groups->groups[i].uid = 0xFFFF;
            _groups->groups[i].bits = 0x00;
            storage_set(STORAGE_AREA_MOBUS_GROUPS, _groups,
                sizeof(struct storage_mobus_groups_info));
            err = true;
            break;
        }
    }

    return err;
}

static inline bool __get_group_by_uuid(mobus_device_t self, uint16_t uuid,
    uint8_t* bits) {
    bool err = false;
    for (char i = 0; i < CONFIG_STORAGE_MOBUS_GROUP_NUMS; i++) {
        if (_groups->groups[i].uid == uuid) {
            *bits = _groups->groups[i].bits;
            err = true;
            break;
        }
    }

    return err;
}

static void __multicast_cfg_add(mobus_device_t self, uint8_t* param,
    uint8_t length) {
    uint16_t uuid = 0;
    uint8_t idx = 0;
    uint8_t channel = 0;
    uint8_t bits = 0;

    if (length >= (sizeof(uuid) + 2)) {
      /* get group uuid */
        uuid = ((uint16_t)param[1] << 8) | param[2];
        length -= sizeof(uuid);
        param += sizeof(uuid);
        for (uint8_t i = 0; i < (length / 2); i++) {
            uint8_t address = param[(i * 2) + 2];
            if (!address) {
                continue;
            }
            address--;
            if (address < self->channel_nums) {
                bits |= (1UL << address);
            }
        }
        if (!__new_group_by_uuid(self, uuid, bits)) {
            xlog_tag_warn(TAG, "No empty group, please delete first\n");
        }
    }
}

static void __multicast_cfg_del(mobus_device_t self, uint8_t* param,
    uint8_t length) {
    uint16_t uuid = 0;

    if (length >= sizeof(uuid)) {
        uuid = ((uint16_t)param[1] << 8) | param[2];
        if (__del_group_by_uuid(self, uuid)) {
            xlog_tag_info(TAG, "Delete group(%04X) successfully\n", uuid);
        }
    }
}

static void __multicast_cfg_read_nums(mobus_device_t self, uint8_t* param,
    uint8_t length) {
    uint8_t buf[CONFIG_STORAGE_MOBUS_GROUP_NUMS * sizeof(uint16_t) + 2] = { 0 };
    uint8_t off = 2;

    buf[0] = MOBUS_GROUP_CONFIG_CMD_READ_NUMS;
    for (uint8_t i = 0; i < CONFIG_STORAGE_MOBUS_GROUP_NUMS; ++i) {
        if (_groups->groups[i].uid != 0xFFFF) {
            buf[1]++;
            buf[off++] = (_groups->groups[i].uid >> 8) & 0xFF;
            buf[off++] = _groups->groups[i].uid & 0xFF;
        }
    }
    protocol_mobus_upload(MOBUS_FRAME_TYPE_UPLOAD_GROUP_ADDR,
        self->native_address, self->channel_nums, 0, buf, off);
}

static void __multicast_cfg(mobus_device_t self, uint8_t sub_cmd,
    uint8_t* param, uint8_t length) {
    void (*cb)(mobus_device_t self, uint8_t * param, uint8_t length) = NULL;

    cb = protocol_callback_find(sub_cmd, _multicast_cfg_cbs,
        ARRAY_SIZE(_multicast_cfg_cbs));
    if (cb) {
        cb(self, param, length);
    }
}

static void __multicast_cmd(mobus_device_t self, uint16_t uid, uint8_t* param,
    uint8_t length) {
    uint8_t bits = 0;
    uint8_t cmd = 0;
    do {
        if (!length) {
            break;
        }
        if (!__get_group_by_uuid(self, uid, &bits)) {
            break;
        }
        cmd = param[0];
        for (char i = 1; i < self->channel_nums; i++) {
            if ((bits >> (i - 1) & 1) == 1) {
                if (cmd == 1) {
                    open_relay_ornot_for_others(i - 1, false);
                }
                else if (cmd == 2) {
                    open_relay_ornot_for_others(i - 1, true);
                }
                else if (cmd == 0x55) {
                    if (no_storage.temp_relay.relay_out[i - 1] == 0) {
                        no_storage.temp_relay.relay_out[i - 1] = 1;
                    }
                    else if (no_storage.temp_relay.relay_out[i - 1] == 1) {
                        no_storage.temp_relay.relay_out[i - 1] = 0;
                    }
                    open_relay_ornot_for_others(i - 1, no_storage.temp_relay.relay_out[i - 1]);
                }
            }
        }
    } while (0);
}
static void _multicast(mobus_device_t self, uint16_t uuid, uint8_t command,
    uint8_t sub_command, uint8_t* param, uint8_t length) {
    _groups = (struct storage_mobus_groups_info*)storage_get(
        STORAGE_AREA_MOBUS_GROUPS);
    assert(_groups);
    if (command == MOBUS_FRAME_TYPE_GROUP) {
        __multicast_cmd(self, uuid, param, length);
    }
    else if (command == MOBUS_FRAME_TYPE_CONFIG_GROUP_ADDR) {
        __multicast_cfg(self, sub_command, param, length);
    }
    __multicast_cfg_read_nums(self, param, length);
}
